﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using Cvs.Compilers;
using CVS.DAL;
using Executors;
using log4net;
using SubmissionsService.Utils;

namespace SubmissionsService
{
    class SubmissionsProcessor
    {
        private static ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        private bool m_stopRequested;

        private readonly ICompiler m_compiler = new CSharpCompiler();

        private readonly SynchronizedHashtable m_processingSubmissionIds;

        public SubmissionsProcessor(SynchronizedHashtable processingSubmissionIds)
        {
            m_processingSubmissionIds = processingSubmissionIds;
        }

        public void ProcessSubmission()
        {
            while (!m_stopRequested)
            {
                try
                {
                    var submissions = DataRepository.GetPendingSubmissions();

                    foreach ( var submission in submissions )
                    {
                        if (submission == null || !this.m_processingSubmissionIds.Add(submission.Id))
                        {
                            WaitToTryAgain();
                            continue;
                        }
                        TryProcessPendingSubmission(submission);
                    }
                }
                catch (Exception e)
                {
                    Logger.Error( e );

                    WaitToTryAgain();
                }
            }
        }

        private void TryProcessPendingSubmission(Submission submission)
        {
            try
            {
                ProcessPendingSubmission(submission);

                m_processingSubmissionIds.Remove(submission.Id);
            }
            catch (Exception)
            {
                m_processingSubmissionIds.Remove(submission.Id);

                throw;
            }
        }

        private void ProcessPendingSubmission(Submission submission)
        {
            UpdateProcessingStartTime( submission );

            var compilationResult = Compile( submission );

            compilationResult = DataRepository.AddCompilationResult(compilationResult);

            var testRuns = new List<TestRun>();
            if ( compilationResult.IsSuccessful )
            {
                var tests = DataRepository.GetTestDefinitionsForSubmission( submission );

                var executor = new DotNetExecutor( );
                var outputs = executor.Execute( tests.Select( t => t.Input ).ToList( ), compilationResult.OutputFile );

                for ( int i = 0; i < outputs.Count; i++ )
                {
                    testRuns.Add( new TestRun
                    {
                        IsSuccessful = outputs[i] == tests[i].ExpectedOutput,
                        Output = outputs[i],
                        SubmissionId = submission.Id,
                        TestDefinitionId = tests[i].Id
                    });
                }

                DataRepository.AddTestRuns( testRuns );
            }
            UpdateSubmission(submission, testRuns, compilationResult);
        }

        private void UpdateProcessingStartTime( Submission submission )
        {
            submission.ProcessingStartTime = DateTime.Now;
            DataRepository.UpdateSubmission(submission);
        }

        private void UpdateSubmission( Submission submission, List< TestRun > testRuns, CompilationResult compilationResult )
        {
            submission.IsProcessed = true;
            submission.CompilationResultId = compilationResult.Id;

            bool compilationSuccessful = compilationResult.IsSuccessful;
            var allTestsPassed = testRuns.Any( ) && testRuns.All( t => t.IsSuccessful );
            submission.IsSuccessful = compilationSuccessful && allTestsPassed;

            DataRepository.UpdateSubmission( submission );
        }

        private CompilationResult Compile( Submission submission )
        {
            var outFile = "" + submission.Id + "_" + submission.TaskId + ".exe";

            return m_compiler.Compile(submission.SourceCode, outFile); 
        }

        public void Stop()
        {
            this.m_stopRequested = true;
        }

        private void WaitToTryAgain()
        {
            Thread.Sleep(1000);
        }
    }
}
